using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Media;
 
using GameLibrary;
 
namespace ArkanoidGame
{
   public class Ball : DynamicElement
   {
       #region  Private Members
       private Color m_outlineColour = BricksSettings.OUTLINE_COLOR;
       private Color m_fillColour = BricksSettings.FILL_COLOR;
       private bool m_super = false;
       #endregion

       #region Public Properties
       public Color OutlineColour
       {
           get { return m_outlineColour; }
           set { m_outlineColour = value; }
       }
 
       public Color FillColour
       {
           get { return m_fillColour; }
           set { m_outlineColour = value; }
       }

       public bool Super 
       {
           get { return m_super; }
           set { m_super = value; }
       }
       #endregion
 
       #region Constructors
       public Ball(GamePanel owningGame, RectangleF rectangle)
           : base(owningGame, rectangle, new PointF(0, 0), new PointF(0, 0))
       {
       }



       public Ball(GamePanel owningGame, RectangleF rectangle, PointF velocity, PointF acceleration, bool super)
           : base(owningGame, rectangle, velocity, acceleration)
       {
           m_super = super;
       }
       
 
       public Ball(GamePanel owningGame, PointF position, SizeF size)
           : this(owningGame, new RectangleF(position, size))
       {
       }
 
       public Ball(GamePanel owningGame, RectangleF rectangle, PointF velocity, PointF acceleration)
           : base(owningGame, rectangle, velocity, acceleration)
       {
       }
 
       public Ball(GamePanel owningGame, PointF position, SizeF size, PointF velocity, PointF acceleration)
           : this(owningGame, new RectangleF(position, size), velocity, acceleration)
       {
       }
       #endregion
 
       #region Public Methods
       public float CalculateNewHorizontalVelocity(PointF position)
       {
           ArkanoidGame owner = (ArkanoidGame)this.OwningGame;
 
           float distanceFromCenter = (owner.Paddle.Rectangle.Left + owner.Paddle.Rectangle.Width / 2f) - (position.X + this.Size.Width / 2f);
           float percent = -1 * distanceFromCenter / (owner.Paddle.Rectangle.Width / 2f);
           float sign = percent / (float)Math.Abs(percent);

           if (percent < -1 || percent > 1) 
           { 
               percent = sign; 
           }
 
           float newXVelocity = (sign * BricksSettings.BALL_MIN_BOUNCE_VELOCITY_X) + (BricksSettings.BALL_MAX_BOUNCE_VELOCITY_X - BricksSettings.BALL_MIN_BOUNCE_VELOCITY_X) * percent;
           return newXVelocity;
       }
       #endregion
 
       #region Private Methods
       private void PerformStateChange(GameState newState)
       {
           ArkanoidGame owner = (ArkanoidGame)this.OwningGame;
 
           switch (newState)
           {
               case GameState.Ready:
                   {
                       string text = "Ball lost!";
                       Font font = new Font(owner.Font.FontFamily, 20, FontStyle.Bold);
                       SizeF textSize = owner.CreateGraphics().MeasureString(text, font);
 
                       owner.HUD.FloatingTextItems.Add(
                           new FloatText(
                               owner,
                               text,
                               font,
                               BricksSettings.OUTLINE_COLOR,
                               new PointF(owner.InitialGameSize.Width / 2f - textSize.Width / 2f, owner.InitialGameSize.Height / 2f - textSize.Height / 2f - 50f),
                               new PointF(0f, -15f),
                               new TimeSpan(0, 0, 3)
                           )
                       );
 
                       owner.HUD.Lives = Math.Max(0, owner.HUD.Lives - 1);
                       owner.ConsecutiveHits = 0;
                       owner.HUD.ScoreMultiplier = 1;

                       using (var soundPlayer = new SoundPlayer(Properties.Resources._1down))
                       {
                           soundPlayer.Play();
                       }
                   }
                   break;
               case GameState.GameOverFailed:
                   {
                       string text = "Game Over!";
                       Font font = new Font(owner.Font.FontFamily, 20, FontStyle.Bold);
                       SizeF textSize = owner.CreateGraphics().MeasureString(text, font);
                       
                       owner.HUD.FloatingTextItems.Add(
                           new FloatText(
                               owner,
                               text,
                               font,
                               BricksSettings.OUTLINE_COLOR,
                               new PointF(owner.InitialGameSize.Width / 2f - textSize.Width / 2f, owner.InitialGameSize.Height / 2f - textSize.Height / 2f),
                               new PointF(0f, -15f),
                               new TimeSpan(0, 0, 3)
                           )
                       );
 
                       owner.HUD.UpdateHighScores();
                       
                       using (var soundPlayer = new SoundPlayer(Properties.Resources.game_over))
                       {
                           soundPlayer.Play();
                       }
                   }
 
                   break;
 
               case GameState.LevelComplete:
                   {
                       owner.ConsecutiveHits = 0;
                       owner.HUD.ScoreMultiplier = 1;
                       string text = "Level Complete!";
                       Font font = new Font(owner.Font.FontFamily, 20, FontStyle.Bold);
                       SizeF textSize = owner.CreateGraphics().MeasureString(text, font);
 
                       owner.HUD.FloatingTextItems.Add(
                           new FloatText(
                               owner,
                               text,
                               font,
                               BricksSettings.OUTLINE_COLOR,
                               new PointF(owner.InitialGameSize.Width / 2f - textSize.Width / 2f, owner.InitialGameSize.Height / 2f - textSize.Height / 2f),
                               new PointF(0f, -15f),
                               new TimeSpan(0, 0, 3)
                           )
                       );

                       using (var soundPlayer = new SoundPlayer(Properties.Resources._1up))
                       {
                           soundPlayer.Play();
                       }
                   }
                   break;
           }
           owner.State = newState;
       }
       #endregion
 
       #region State Update Methods
       private void UpdateForReady(TimeSpan elapsedTime)
       {
           ArkanoidGame owner = (ArkanoidGame)this.OwningGame;
           float ballY = owner.Paddle.Rectangle.Top - BricksSettings.BRICK_PADDING_Y - this.Rectangle.Height;
           float ballX = this.Position.X;

           if (this.Rectangle.Left + this.Rectangle.Width / 2f < owner.Paddle.Rectangle.Left)
           {
               ballX = owner.Paddle.Rectangle.Left - this.Rectangle.Width / 2f;
           }
           else
           {
               if (this.Rectangle.Left + this.Rectangle.Width / 2f > owner.Paddle.Rectangle.Right)
               {
                   ballX = owner.Paddle.Rectangle.Right - this.Rectangle.Width / 2f;
               }
           }
 
           this.Position = new PointF(ballX, ballY);
       }
 
       private void UpdateForPlaying(TimeSpan elapsedTime)
       {
           ArkanoidGame owner = (ArkanoidGame)this.OwningGame;
           PointF nextPosition = this.CalculateNextPosition(elapsedTime);
 
           bool needRecalc = false;
           bool brickHit = false;
           List<Brick> doomedBricks = new List<Brick>();
           foreach (Brick brick in owner.Bricks)
           {
               if (brick.Rectangle.IntersectsWith(new RectangleF(nextPosition, this.Size)))
               { 
                   doomedBricks.Add(brick); 
               }
               
           }
 
           if (doomedBricks.Count > 0)
           {
               brickHit = true;
               foreach (Brick brick in doomedBricks)
               {
                   if (!needRecalc && !m_super)
                   {
                       if (this.Rectangle.Left > brick.Rectangle.Right || this.Rectangle.Right < brick.Rectangle.Left)
                       {
                           this.Velocity = new PointF(this.Velocity.X * -1f, this.Velocity.Y);
                       }
                       else 
                       { 
                           this.Velocity = new PointF(this.Velocity.X, this.Velocity.Y * -1f);
                       }
 
                       needRecalc = true;
                   }
 
                   owner.ConsecutiveHits++;

                   if (owner.ConsecutiveHits % 2 == 0) 
                   { 
                       owner.HUD.ScoreMultiplier = (int)Math.Min(BricksSettings.SCORE_MULTIPLIER_MAX, owner.HUD.ScoreMultiplier + 1);
                   }
 
                   int scoreInc = BricksSettings.SCORE_BASIC_BRICK * owner.HUD.ScoreMultiplier;
                   owner.HUD.Score += scoreInc;
                   string scoreText = string.Format("{0} ({1}x)", scoreInc, owner.HUD.ScoreMultiplier);
                   SizeF scoreTextSize = owner.CreateGraphics().MeasureString(scoreText, owner.Font);
 
                   owner.HUD.FloatingTextItems.Add(
                       new FloatText(
                           owner,
                           scoreText,
                           owner.Font,
                           BricksSettings.OUTLINE_COLOR,
                           new PointF(
                               (brick.Rectangle.Left + brick.Rectangle.Width / 2f) - (scoreTextSize.Width / 2f),
                               (brick.Rectangle.Top + brick.Rectangle.Height / 2f) - (scoreTextSize.Height / 2f)
                           ),
                           new PointF(0, -15),
                           new TimeSpan(0, 0, 2)
                       )
                   );
 
                   owner.HUD.ScoreAccumulator += scoreInc;
                   if (owner.HUD.ScoreAccumulator >= BricksSettings.SCORE_AMOUNT_PER_LIFE)
                   {
                       owner.HUD.ScoreAccumulator = owner.HUD.ScoreAccumulator - BricksSettings.SCORE_AMOUNT_PER_LIFE;
 
                       if (owner.HUD.Lives < BricksSettings.LIVES_MAX)
                       {
                           owner.HUD.Lives = owner.HUD.Lives + 1;
 
                           string text = "Bonus Life!";
                           SizeF textSize = owner.CreateGraphics().MeasureString(text, owner.Font);
 
                           owner.HUD.FloatingTextItems.Add(
                               new FloatText(
                                   owner,
                                   text,
                                   owner.Font,
                                   owner.HUD.ForeColour,
                                   new PointF(
                                       owner.InitialGameSize.Width / 2f - textSize.Width / 2f,
                                       owner.InitialGameSize.Height / 2f - textSize.Height / 2f
                                   ),
                                   new PointF(0, -15),
                                   new TimeSpan(0, 0, 2)
                               )
                           );
                       }
                   }
 
                   owner.Bricks.Remove(brick);

                   using (var soundPlayer = new SoundPlayer(Properties.Resources.hit_brick))
                   {
                       soundPlayer.Play();
                   }
               }
           }
           else
           {
 
               if (nextPosition.X < 0 || nextPosition.X + this.Size.Width > this.OwningGame.ClientRectangle.Width)
               {
                   this.Velocity = new PointF(this.Velocity.X * -1, this.Velocity.Y);
                   needRecalc = true;
                   
                   using (var soundPlayer = new SoundPlayer(Properties.Resources.hit))
                   {
                       soundPlayer.Play();
                   }
               }
 
               if (nextPosition.Y + this.Size.Height > owner.Paddle.Rectangle.Top && nextPosition.X + this.Size.Width > owner.Paddle.Rectangle.Left && nextPosition.X < owner.Paddle.Rectangle.Right && this.Velocity.Y > 0)
               {
                   owner.ConsecutiveHits = 0;
                   owner.HUD.ScoreMultiplier = 1;
 
                   this.Velocity = new PointF(CalculateNewHorizontalVelocity(nextPosition), this.Velocity.Y * -1);
                   needRecalc = true;
                   
                   using (var soundPlayer = new SoundPlayer(Properties.Resources.hit))
                   {
                       soundPlayer.Play();
                   }
               }
 
               if (nextPosition.Y < 0)
               {
                   // Top of the screen, bounce downwards.
                   this.Velocity = new PointF(this.Velocity.X, this.Velocity.Y * -1);
                   needRecalc = true;
                   
                   using (var soundPlayer = new SoundPlayer(Properties.Resources.hit))
                   {
                       soundPlayer.Play();
                   }
               }
               else if (nextPosition.Y + this.Size.Height > owner.HUD.Rectangle.Top)
               {
                   // Off the bottom of the screen... decrement lives and change state.
                   PerformStateChange(GameState.Ready);
                   
                   if (owner.HUD.Lives == 0)
                   {
                       PerformStateChange(GameState.GameOverFailed);
                   }
                        
               }
           }
 
           if (brickHit && this.Velocity.Y < BricksSettings.BALL_VELOCITY_Y_MAX)
           {
               float velYSign = this.Velocity.Y / (float)Math.Abs(this.Velocity.Y);
               float velYNew = this.Velocity.Y + (velYSign * BricksSettings.BALL_VECLOITY_HIT_INCREMENT_Y);
 
               if (this.Velocity.Y > BricksSettings.BALL_VELOCITY_Y_MAX)
               {
                   velYNew = BricksSettings.BALL_VELOCITY_Y_MAX;
               }
 
               this.Velocity = new PointF(this.Velocity.X, velYNew);
           }

           if (brickHit)
           {
               if(new Random().Next(1, 11) == 10)
               {
                   owner.Bonuses.Add
                   (
                       new Bonus(this.OwningGame,
                       RandomBonusTypeGenerator.GetRandomBonusType(),
                       this.Position)
                   );
               }  
           }

           if (needRecalc)
           {
               nextPosition = this.CalculateNextPosition(elapsedTime);
           }
 
           this.Position = nextPosition;
           this.Velocity = this.CalculateNextVelocity(elapsedTime);

           if (owner.Bricks.Count == 0)
           {
               PerformStateChange(GameState.LevelComplete);
           }
       }
       #endregion
 
       #region IGameElement Implementation
       public override void Draw(TimeSpan elapsedTime, Graphics g)
       {
           if (g != null)
           {
               if (!m_super)
               {
                   g.FillEllipse(new SolidBrush(this.FillColour), this.Rectangle);
               }
               else 
               {
                   g.FillEllipse(new SolidBrush(this.OutlineColour), this.Rectangle);
               }
               g.DrawEllipse(new Pen(this.OutlineColour), this.Rectangle);
           }
       }
 
       public override void Update(TimeSpan elapsedTime)
       {
           ArkanoidGame owner = (ArkanoidGame)this.OwningGame;
 
           switch (owner.State)
           {
               case GameState.Ready:
                   UpdateForReady(elapsedTime);
                   break;
 
               case GameState.Playing:
                   UpdateForPlaying(elapsedTime);
                   break;
           }
       }
       #endregion
   }
}